/* Copyright 2023 Dual Tachyon
 * https://github.com/DualTachyon
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *     Unless required by applicable law or agreed to in writing, software
 *     distributed under the License is distributed on an "AS IS" BASIS,
 *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *     See the License for the specific language governing permissions and
 *     limitations under the License.
 */

#include "driver/bk4819-regs.h"
#include <string.h>

#include "am_fix.h"
#include "app/dtmf.h"
#ifdef ENABLE_FMRADIO
    #include "app/fm.h"
#endif
#include "audio.h"
#include "bsp/dp32g030/gpio.h"
#include "dcs.h"
#include "driver/bk4819.h"
#include "driver/eeprom.h"
#include "driver/gpio.h"
#include "driver/system.h"
#include "frequencies.h"
#include "functions.h"
#include "helper/battery.h"
#include "misc.h"
#include "radio.h"
#include "settings.h"
#include "ui/menu.h"

VFO_Info_t    *gTxVfo;
VFO_Info_t    *gRxVfo;
VFO_Info_t    *gCurrentVfo;
DCS_CodeType_t gCurrentCodeType;
VfoState_t     VfoState[2];

const char gModulationStr[MODULATION_UKNOWN][4] = {
    [MODULATION_FM]="FM",
    [MODULATION_AM]="AM",
    [MODULATION_USB]="USB",

#ifdef ENABLE_BYP_RAW_DEMODULATORS
    [MODULATION_BYP]="BYP",
    [MODULATION_RAW]="RAW"
#endif
};

bool RADIO_CheckValidChannel(uint16_t channel, bool checkScanList, uint8_t scanList)
{
    // return true if the channel appears valid
    if (!IS_MR_CHANNEL(channel))
        return false;

    const ChannelAttributes_t att = gMR_ChannelAttributes[channel];

    if (checkScanList && gMR_ChannelExclude[channel] == true)
        return false;

    if (att.band > BAND7_470MHz)
        return false;

    if (!checkScanList || scanList > 4)
        return true;

    /*
    if(scanList == 0 && (att.scanlist1 == 1 || att.scanlist2 == 1 || att.scanlist3 == 1))
    {
        return false;
    }
    else if(scanList == 1 && att.scanlist1 != 1)
    {
        return false;
    }
    else if(scanList == 2 && att.scanlist2 != 1)
    {
        return false;
    }
    else if(scanList == 3 && att.scanlist3 != 1)
    {
        return false;
    }
    else if(scanList == 4 && (att.scanlist1 == 0 && att.scanlist2 == 0 && att.scanlist3 == 0))
    {
        return false;
    }
    */

    if ((scanList == 0 && (att.scanlist1 == 1 || att.scanlist2 == 1 || att.scanlist3 == 1)) ||
        (scanList == 1 && att.scanlist1 != 1) ||
        (scanList == 2 && att.scanlist2 != 1) ||
        (scanList == 3 && att.scanlist3 != 1) ||
        (scanList == 4 && (att.scanlist1 == 0 && att.scanlist2 == 0 && att.scanlist3 == 0))) {
        return false;
    }

    //return true;

    // I don't understand what this code is for...
    
    const uint8_t PriorityCh1 = gEeprom.SCANLIST_PRIORITY_CH1[scanList - 1];
    const uint8_t PriorityCh2 = gEeprom.SCANLIST_PRIORITY_CH2[scanList - 1];

    return PriorityCh1 != channel && PriorityCh2 != channel;
}

uint8_t RADIO_FindNextChannel(uint8_t Channel, int8_t Direction, bool bCheckScanList, uint8_t VFO)
{
    for (unsigned int i = 0; IS_MR_CHANNEL(i); i++, Channel += Direction) {
        if (Channel == 0xFF) {
            Channel = MR_CHANNEL_LAST;
        } else if (!IS_MR_CHANNEL(Channel)) {
            Channel = MR_CHANNEL_FIRST;
        }

        if (RADIO_CheckValidChannel(Channel, bCheckScanList, VFO)) {
            return Channel;
        }
    }

    return 0xFF;
}

void RADIO_InitInfo(VFO_Info_t *pInfo, const uint8_t ChannelSave, const uint32_t Frequency)
{
    memset(pInfo, 0, sizeof(*pInfo));

    pInfo->Band                     = FREQUENCY_GetBand(Frequency);
    pInfo->SCANLIST1_PARTICIPATION  = false;
    pInfo->SCANLIST2_PARTICIPATION  = false;
    pInfo->SCANLIST3_PARTICIPATION  = false;
    pInfo->STEP_SETTING             = STEP_12_5kHz;
    pInfo->StepFrequency            = gStepFrequencyTable[pInfo->STEP_SETTING];
    pInfo->CHANNEL_SAVE             = ChannelSave;
    pInfo->FrequencyReverse         = false;
    pInfo->TX_LOCK                  = true;
    pInfo->OUTPUT_POWER             = OUTPUT_POWER_LOW1;
    pInfo->freq_config_RX.Frequency = Frequency;
    pInfo->freq_config_TX.Frequency = Frequency;
    pInfo->pRX                      = &pInfo->freq_config_RX;
    pInfo->pTX                      = &pInfo->freq_config_TX;
    pInfo->Compander                = 0;  // off

    if (ChannelSave == (FREQ_CHANNEL_FIRST + BAND2_108MHz))
        pInfo->Modulation = MODULATION_AM;
    else
        pInfo->Modulation = MODULATION_FM;

    RADIO_ConfigureSquelchAndOutputPower(pInfo);
}

void RADIO_ConfigureChannel(const unsigned int VFO, const unsigned int configure)
{
    VFO_Info_t *pVfo = &gEeprom.VfoInfo[VFO];

    if (!gSetting_350EN) {
        if (gEeprom.FreqChannel[VFO] == FREQ_CHANNEL_FIRST + BAND5_350MHz)
            gEeprom.FreqChannel[VFO] = FREQ_CHANNEL_FIRST + BAND6_400MHz;

        if (gEeprom.ScreenChannel[VFO] == FREQ_CHANNEL_FIRST + BAND5_350MHz)
            gEeprom.ScreenChannel[VFO] = FREQ_CHANNEL_FIRST + BAND6_400MHz;
    }

    uint8_t channel = gEeprom.ScreenChannel[VFO];

    if (IS_VALID_CHANNEL(channel)) {
#ifdef ENABLE_NOAA
        if (IS_NOAA_CHANNEL(channel))
        {
            RADIO_InitInfo(pVfo, gEeprom.ScreenChannel[VFO], NoaaFrequencyTable[channel - NOAA_CHANNEL_FIRST]);

            if (gEeprom.CROSS_BAND_RX_TX == CROSS_BAND_OFF)
                return;

            gEeprom.CROSS_BAND_RX_TX = CROSS_BAND_OFF;

            gUpdateStatus = true;
            return;
        }
#endif

        if (IS_MR_CHANNEL(channel)) {
            channel = RADIO_FindNextChannel(channel, RADIO_CHANNEL_UP, false, VFO);
            if (channel == 0xFF) {
                channel                    = gEeprom.FreqChannel[VFO];
                gEeprom.ScreenChannel[VFO] = gEeprom.FreqChannel[VFO];
            }
            else {
                gEeprom.ScreenChannel[VFO] = channel;
                gEeprom.MrChannel[VFO]     = channel;
            }
        }
    }
    else
        channel = FREQ_CHANNEL_LAST - 1;

    ChannelAttributes_t att = gMR_ChannelAttributes[channel];
    if (att.__val == 0xFF) { // invalid/unused channel
        if (IS_MR_CHANNEL(channel)) {
            channel                    = gEeprom.FreqChannel[VFO];
            gEeprom.ScreenChannel[VFO] = channel;
        }

        uint8_t bandIdx = channel - FREQ_CHANNEL_FIRST;
        RADIO_InitInfo(pVfo, channel, frequencyBandTable[bandIdx].lower);
        return;
    }

    uint8_t band = att.band;
    if (band > BAND7_470MHz) {
        band = BAND6_400MHz;
    }

    bool bParticipation1;
    bool bParticipation2;
    bool bParticipation3;

    if (IS_MR_CHANNEL(channel)) {
        bParticipation1 = att.scanlist1;
        bParticipation2 = att.scanlist2;
        bParticipation3 = att.scanlist3;
    }
    else {
        band = channel - FREQ_CHANNEL_FIRST;
        bParticipation1 = true;
        bParticipation2 = true;
        bParticipation3 = true;
    }

    pVfo->Band                    = band;
    pVfo->SCANLIST1_PARTICIPATION = bParticipation1;
    pVfo->SCANLIST2_PARTICIPATION = bParticipation2;
    pVfo->SCANLIST3_PARTICIPATION = bParticipation3;
    pVfo->CHANNEL_SAVE            = channel;

    uint16_t base;
    if (IS_MR_CHANNEL(channel))
        base = channel * 16;
    else
        base = 0x0C80 + ((channel - FREQ_CHANNEL_FIRST) * 32) + (VFO * 16);

    if (configure == VFO_CONFIGURE_RELOAD || IS_FREQ_CHANNEL(channel))
    {
        uint8_t tmp;
        uint8_t data[8];
        
        // ***************

        EEPROM_ReadBuffer(base + 8, data, sizeof(data));

        tmp = data[3] & 0x0F;
        if (tmp > TX_OFFSET_FREQUENCY_DIRECTION_SUB)
            tmp = 0;
        pVfo->TX_OFFSET_FREQUENCY_DIRECTION = tmp;
        tmp = data[3] >> 4;
        if (tmp >= MODULATION_UKNOWN)
            tmp = MODULATION_FM;
        pVfo->Modulation = tmp;

        tmp = data[6];
        if (tmp >= STEP_N_ELEM)
            tmp = STEP_12_5kHz;
        pVfo->STEP_SETTING  = tmp;
        pVfo->StepFrequency = gStepFrequencyTable[tmp];

        tmp = data[7];
#ifndef ENABLE_FEAT_F4HWN
        if (tmp > (ARRAY_SIZE(gSubMenu_SCRAMBLER) - 1))
            tmp = 0;
        pVfo->SCRAMBLING_TYPE = tmp;
#else
        pVfo->SCRAMBLING_TYPE = 0;
#endif

        pVfo->freq_config_RX.CodeType = (data[2] >> 0) & 0x0F;
        pVfo->freq_config_TX.CodeType = (data[2] >> 4) & 0x0F;

        tmp = data[0];
        switch (pVfo->freq_config_RX.CodeType)
        {
            default:
            case CODE_TYPE_OFF:
                pVfo->freq_config_RX.CodeType = CODE_TYPE_OFF;
                tmp = 0;
                break;

            case CODE_TYPE_CONTINUOUS_TONE:
                if (tmp > (ARRAY_SIZE(CTCSS_Options) - 1))
                    tmp = 0;
                break;

            case CODE_TYPE_DIGITAL:
            case CODE_TYPE_REVERSE_DIGITAL:
                if (tmp > (ARRAY_SIZE(DCS_Options) - 1))
                    tmp = 0;
                break;
        }
        pVfo->freq_config_RX.Code = tmp;

        tmp = data[1];
        switch (pVfo->freq_config_TX.CodeType)
        {
            default:
            case CODE_TYPE_OFF:
                pVfo->freq_config_TX.CodeType = CODE_TYPE_OFF;
                tmp = 0;
                break;

            case CODE_TYPE_CONTINUOUS_TONE:
                if (tmp > (ARRAY_SIZE(CTCSS_Options) - 1))
                    tmp = 0;
                break;

            case CODE_TYPE_DIGITAL:
            case CODE_TYPE_REVERSE_DIGITAL:
                if (tmp > (ARRAY_SIZE(DCS_Options) - 1))
                    tmp = 0;
                break;
        }
        pVfo->freq_config_TX.Code = tmp;

        if (data[4] == 0xFF)
        {
            pVfo->FrequencyReverse  = false;
            pVfo->CHANNEL_BANDWIDTH = BK4819_FILTER_BW_WIDE;
            pVfo->OUTPUT_POWER      = OUTPUT_POWER_LOW1;
            pVfo->BUSY_CHANNEL_LOCK = false;
            pVfo->TX_LOCK = true;
        }
        else
        {
            const uint8_t d4 = data[4];
            pVfo->FrequencyReverse  = !!((d4 >> 0) & 1u);
            pVfo->CHANNEL_BANDWIDTH = !!((d4 >> 1) & 1u);
            pVfo->OUTPUT_POWER      =   ((d4 >> 2) & 7u);
            pVfo->BUSY_CHANNEL_LOCK = !!((d4 >> 5) & 1u);
            pVfo->TX_LOCK           = !!((d4 >> 6) & 1u);
        }

        if (data[5] == 0xFF)
        {
#ifdef ENABLE_DTMF_CALLING
            pVfo->DTMF_DECODING_ENABLE = false;
#endif
            pVfo->DTMF_PTT_ID_TX_MODE  = PTT_ID_OFF;
        }
        else
        {
#ifdef ENABLE_DTMF_CALLING
            pVfo->DTMF_DECODING_ENABLE = ((data[5] >> 0) & 1u) ? true : false;
#endif
            uint8_t pttId = ((data[5] >> 1) & 7u);
            pVfo->DTMF_PTT_ID_TX_MODE  = pttId < ARRAY_SIZE(gSubMenu_PTT_ID) ? pttId : PTT_ID_OFF;
        }

        // ***************

        struct {
            uint32_t Frequency;
            uint32_t Offset;
        } __attribute__((packed)) info;
        EEPROM_ReadBuffer(base, &info, sizeof(info));
        if(info.Frequency==0xFFFFFFFF)
            pVfo->freq_config_RX.Frequency = frequencyBandTable[band].lower;
        else
            pVfo->freq_config_RX.Frequency = info.Frequency;

        if (info.Offset >= _1GHz_in_KHz)
            info.Offset = _1GHz_in_KHz / 100;

        pVfo->TX_OFFSET_FREQUENCY = info.Offset;

        // ***************
    }

    uint32_t frequency = pVfo->freq_config_RX.Frequency;

    // fix previously set incorrect band
    band = FREQUENCY_GetBand(frequency);

    if (frequency < frequencyBandTable[band].lower)
        frequency = frequencyBandTable[band].lower;
    else if (frequency > frequencyBandTable[band].upper)
        frequency = frequencyBandTable[band].upper;
    else if (channel >= FREQ_CHANNEL_FIRST)
        frequency = FREQUENCY_RoundToStep(frequency, pVfo->StepFrequency);

    pVfo->freq_config_RX.Frequency = frequency;

    if (frequency >= frequencyBandTable[BAND2_108MHz].upper && frequency < frequencyBandTable[BAND2_108MHz].upper)
        pVfo->TX_OFFSET_FREQUENCY_DIRECTION = TX_OFFSET_FREQUENCY_DIRECTION_OFF;
    else if (!IS_MR_CHANNEL(channel))
        pVfo->TX_OFFSET_FREQUENCY = FREQUENCY_RoundToStep(pVfo->TX_OFFSET_FREQUENCY, pVfo->StepFrequency);

    RADIO_ApplyOffset(pVfo);

    if (IS_MR_CHANNEL(channel))
    {   // 16 bytes allocated to the channel name but only 10 used, the rest are 0's
        SETTINGS_FetchChannelName(pVfo->Name, channel);
    }

    if (!pVfo->FrequencyReverse)
    {
        pVfo->pRX = &pVfo->freq_config_RX;
        pVfo->pTX = &pVfo->freq_config_TX;
    }
    else
    {
        pVfo->pRX = &pVfo->freq_config_TX;
        pVfo->pTX = &pVfo->freq_config_RX;
    }

    if (!gSetting_350EN)
    {
        FREQ_Config_t *pConfig = pVfo->pRX;
        if (pConfig->Frequency >= 35000000 && pConfig->Frequency < 40000000)
            pConfig->Frequency = 43300000;
    }

    pVfo->Compander = att.compander;

    #ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
    if(gRemoveOffset)
    {
        pVfo->pTX = &pVfo->freq_config_RX;
    }

    if(gPowerHigh)
    {
        pVfo->OUTPUT_POWER = OUTPUT_POWER_HIGH;
    }
    #endif

    RADIO_ConfigureSquelchAndOutputPower(pVfo);
}

void RADIO_ConfigureSquelchAndOutputPower(VFO_Info_t *pInfo)
{


    // *******************************
    // squelch

    FREQUENCY_Band_t Band = FREQUENCY_GetBand(pInfo->pRX->Frequency);
    uint16_t Base = (Band < BAND4_174MHz) ? 0x1E60 : 0x1E00;

    if (gEeprom.SQUELCH_LEVEL == 0)
    {   // squelch == 0 (off)
        pInfo->SquelchOpenRSSIThresh    = 0;     // 0 ~ 255
        pInfo->SquelchOpenNoiseThresh   = 127;   // 127 ~ 0
        pInfo->SquelchCloseGlitchThresh = 255;   // 255 ~ 0

        pInfo->SquelchCloseRSSIThresh   = 0;     // 0 ~ 255
        pInfo->SquelchCloseNoiseThresh  = 127;   // 127 ~ 0
        pInfo->SquelchOpenGlitchThresh  = 255;   // 255 ~ 0
    }
    else
    {   // squelch >= 1
        Base += gEeprom.SQUELCH_LEVEL;                                        // my eeprom squelch-1
                                                                              // VHF   UHF
        EEPROM_ReadBuffer(Base + 0x00, &pInfo->SquelchOpenRSSIThresh,    1);  //  50    10
        EEPROM_ReadBuffer(Base + 0x10, &pInfo->SquelchCloseRSSIThresh,   1);  //  40     5

        EEPROM_ReadBuffer(Base + 0x20, &pInfo->SquelchOpenNoiseThresh,   1);  //  65    90
        EEPROM_ReadBuffer(Base + 0x30, &pInfo->SquelchCloseNoiseThresh,  1);  //  70   100

        EEPROM_ReadBuffer(Base + 0x40, &pInfo->SquelchCloseGlitchThresh, 1);  //  90    90
        EEPROM_ReadBuffer(Base + 0x50, &pInfo->SquelchOpenGlitchThresh,  1);  // 100   100


        uint16_t noise_open   = pInfo->SquelchOpenNoiseThresh;
        uint16_t noise_close  = pInfo->SquelchCloseNoiseThresh;

#if ENABLE_SQUELCH_MORE_SENSITIVE
        uint16_t rssi_open    = pInfo->SquelchOpenRSSIThresh;
        uint16_t rssi_close   = pInfo->SquelchCloseRSSIThresh;
        uint16_t glitch_open  = pInfo->SquelchOpenGlitchThresh;
        uint16_t glitch_close = pInfo->SquelchCloseGlitchThresh;
        // make squelch more sensitive
        // note that 'noise' and 'glitch' values are inverted compared to 'rssi' values
        rssi_open   = (rssi_open   * 1) / 2;
        noise_open  = (noise_open  * 2) / 1;
        glitch_open = (glitch_open * 2) / 1;

        // ensure the 'close' threshold is lower than the 'open' threshold
        if (rssi_close == rssi_open && rssi_close >= 2)
            rssi_close -= 2;
        if (noise_close == noise_open && noise_close  <= 125)
            noise_close += 2;
        if (glitch_close == glitch_open && glitch_close <= 253)
            glitch_close += 2;

        pInfo->SquelchOpenRSSIThresh    = (rssi_open    > 255) ? 255 : rssi_open;
        pInfo->SquelchCloseRSSIThresh   = (rssi_close   > 255) ? 255 : rssi_close;
        pInfo->SquelchOpenGlitchThresh  = (glitch_open  > 255) ? 255 : glitch_open;
        pInfo->SquelchCloseGlitchThresh = (glitch_close > 255) ? 255 : glitch_close;
#endif

        pInfo->SquelchOpenNoiseThresh   = (noise_open   > 127) ? 127 : noise_open;
        pInfo->SquelchCloseNoiseThresh  = (noise_close  > 127) ? 127 : noise_close;
    }

    // *******************************
    // output power

    Band = FREQUENCY_GetBand(pInfo->pTX->Frequency);

    // my eeprom calibration data 
    //
    // 1ED0 32 32 32 64 64 64 8c 8c 8c ff ff ff ff ff ff ff  50 MHz
    // 1EE0 32 32 32 64 64 64 8c 8c 8c ff ff ff ff ff ff ff 108 MHz
    // 1EF0 5f 5f 5f 69 69 69 87 87 87 ff ff ff ff ff ff ff 137 MHz
    // 1F00 32 32 32 64 64 64 8c 8c 8c ff ff ff ff ff ff ff 174 MHz
    // 1F10 5f 5f 5f 69 69 69 87 87 87 ff ff ff ff ff ff ff 350 MHz
    // 1F20 5f 5f 5f 69 69 69 87 87 87 ff ff ff ff ff ff ff 400 MHz
    // 1F30 32 32 32 64 64 64 8c 8c 8c ff ff ff ff ff ff ff 470 MHz

    uint8_t Txp[3];
    uint8_t Op = 0; // Low eeprom calibration data 
    uint8_t currentPower = pInfo->OUTPUT_POWER;

    if(currentPower == OUTPUT_POWER_USER)
    {
        if(gSetting_set_pwr == 5)
        {
            Op = 1; // Mid eeprom calibration data
        }
        else if(gSetting_set_pwr == 6)
        {
            Op = 2; // High eeprom calibration data
        }
        currentPower = gSetting_set_pwr;
    }
    else
    {
        if (currentPower == OUTPUT_POWER_MID)
        {
            Op = 1; // Mid eeprom calibration data
        }
        else if(currentPower == OUTPUT_POWER_HIGH)
        {
            Op = 2; // High eeprom calibration data
        }
        currentPower--;
    }

    EEPROM_ReadBuffer(0x1ED0 + (Band * 16) + (Op * 3), Txp, 3);

#ifdef ENABLE_FEAT_F4HWN
    // make low and mid even lower
    // and use calibration values 
    // be aware with toxic fucking closed firmwares

    /*
    uint8_t shift[] = {0, 0, 0, 0, 0};

    if(Band == 5) // UHF
    {
        shift[0] = 0;
        shift[1] = 0;
        shift[2] = 0;
        shift[3] = 0;
        shift[4] = 0;
    }
    */

    /*
    for(uint8_t p = 0; p < 3; p++)
    {
        switch (currentPower)
        {
            case 0:
                Txp[p] = (Txp[p] * 4) / 25; //+ shift[pInfo->OUTPUT_POWER]; 
                break;
            case 1:
                Txp[p] = (Txp[p] * 4) / 19; // + shift[pInfo->OUTPUT_POWER];
                break;
            case 2:
                Txp[p] = (Txp[p] * 4) / 13; // + shift[pInfo->OUTPUT_POWER];
                break;
            case 3:
                Txp[p] = (Txp[p] * 4) / 10; // + shift[pInfo->OUTPUT_POWER];
                break;
            case 4:
                Txp[p] = (Txp[p] * 4) / 7; // + shift[pInfo->OUTPUT_POWER];
                break;
            case 5:
                Txp[p] = (Txp[p] * 3) / 4;
                break;
            case 6:
                Txp[p] = Txp[p] + 30;
                break;              
        }
    }
    */

    static const uint8_t dividers[6] = { 25, 19, 13, 10, 7, 4};

    for (uint8_t p = 0; p < 3; p++)
    {
        if (currentPower < 6)
        {
            Txp[p] = (Txp[p] * (currentPower == 5 ? 3 : 4)) / dividers[currentPower];
        }
        else // case 6
        {
            Txp[p] += 30;
        }
    }
#else
    #ifdef ENABLE_REDUCE_LOW_MID_TX_POWER
        // make low and mid even lower
        if (pInfo->OUTPUT_POWER == OUTPUT_POWER_LOW) {
            Txp[0] /= 5;
            Txp[1] /= 5;
            Txp[2] /= 5;
        }
        else if (pInfo->OUTPUT_POWER == OUTPUT_POWER_MID){
            Txp[0] /= 3;
            Txp[1] /= 3;
            Txp[2] /= 3;
        }
    #endif
#endif

    pInfo->TXP_CalculatedSetting = FREQUENCY_CalculateOutputPower(
        Txp[0],
        Txp[1],
        Txp[2],
         frequencyBandTable[Band].lower,
        (frequencyBandTable[Band].lower + frequencyBandTable[Band].upper) / 2,
         frequencyBandTable[Band].upper,
        pInfo->pTX->Frequency);

    // *******************************
}

void RADIO_ApplyOffset(VFO_Info_t *pInfo)
{
    uint32_t Frequency = pInfo->freq_config_RX.Frequency;

    switch (pInfo->TX_OFFSET_FREQUENCY_DIRECTION)
    {
        case TX_OFFSET_FREQUENCY_DIRECTION_OFF:
            break;
        case TX_OFFSET_FREQUENCY_DIRECTION_ADD:
            Frequency += pInfo->TX_OFFSET_FREQUENCY;
            break;
        case TX_OFFSET_FREQUENCY_DIRECTION_SUB:
            Frequency -= pInfo->TX_OFFSET_FREQUENCY;
            break;
    }

    pInfo->freq_config_TX.Frequency = Frequency;
}

static void RADIO_SelectCurrentVfo(void)
{
    // if crossband is active and DW not the gCurrentVfo is gTxVfo (gTxVfo/TX_VFO is only ever changed by the user)
    // otherwise it is set to gRxVfo which is set to gTxVfo in RADIO_SelectVfos
    // so in the end gCurrentVfo is equal to gTxVfo unless dual watch changes it on incomming transmition (again, this can only happen when XB off)
    // note: it is called only in certain situations so could be not up-to-date
    gCurrentVfo = (gEeprom.CROSS_BAND_RX_TX == CROSS_BAND_OFF || gEeprom.DUAL_WATCH != DUAL_WATCH_OFF) ? gRxVfo : gTxVfo;
}

void RADIO_SelectVfos(void)
{
    // if crossband without DW is used then RX_VFO is the opposite to the TX_VFO
    gEeprom.RX_VFO = (gEeprom.CROSS_BAND_RX_TX == CROSS_BAND_OFF || gEeprom.DUAL_WATCH != DUAL_WATCH_OFF) ? gEeprom.TX_VFO : !gEeprom.TX_VFO;

    gTxVfo = &gEeprom.VfoInfo[gEeprom.TX_VFO];
    gRxVfo = &gEeprom.VfoInfo[gEeprom.RX_VFO];

    RADIO_SelectCurrentVfo();
}

void RADIO_SetupRegisters(bool switchToForeground)
{
    BK4819_FilterBandwidth_t Bandwidth = gRxVfo->CHANNEL_BANDWIDTH;

    #ifdef ENABLE_FEAT_F4HWN_NARROWER
        if(Bandwidth == BK4819_FILTER_BW_NARROW && gSetting_set_nfm == 1)
        {
            Bandwidth = BK4819_FILTER_BW_NARROWER;
        }
    #endif

    AUDIO_AudioPathOff();

    gEnableSpeaker = false;

    BK4819_ToggleGpioOut(BK4819_GPIO6_PIN2_GREEN, false);

    switch (Bandwidth)
    {
        default:
            Bandwidth = BK4819_FILTER_BW_WIDE;
            [[fallthrough]];
        case BK4819_FILTER_BW_WIDE:
        case BK4819_FILTER_BW_NARROW:
        case BK4819_FILTER_BW_NARROWER:
            #ifdef ENABLE_AM_FIX
//              BK4819_SetFilterBandwidth(Bandwidth, gRxVfo->Modulation == MODULATION_AM && gSetting_AM_fix);
                BK4819_SetFilterBandwidth(Bandwidth, true);
            #else
                BK4819_SetFilterBandwidth(Bandwidth, false);
            #endif
            break;
    }

    BK4819_ToggleGpioOut(BK4819_GPIO5_PIN1_RED, false);

    BK4819_SetupPowerAmplifier(0, 0);

    BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, false);

    while (1)
    {
        const uint16_t Status = BK4819_ReadRegister(BK4819_REG_0C);
        if ((Status & 1u) == 0) // INTERRUPT REQUEST
            break;

        BK4819_WriteRegister(BK4819_REG_02, 0);
        SYSTEM_DelayMs(1);
    }
    BK4819_WriteRegister(BK4819_REG_3F, 0);

    // mic gain 0.5dB/step 0 to 31
    BK4819_WriteRegister(BK4819_REG_7D, 0xE940 | (gEeprom.MIC_SENSITIVITY_TUNING & 0x1f));

    uint32_t Frequency;
    #ifdef ENABLE_NOAA
        if (!IS_NOAA_CHANNEL(gRxVfo->CHANNEL_SAVE) || !gIsNoaaMode)
            Frequency = gRxVfo->pRX->Frequency;
        else
            Frequency = NoaaFrequencyTable[gNoaaChannel];
    #else
        Frequency = gRxVfo->pRX->Frequency;
    #endif
    BK4819_SetFrequency(Frequency);

    BK4819_SetupSquelch(
        gRxVfo->SquelchOpenRSSIThresh,    gRxVfo->SquelchCloseRSSIThresh,
        gRxVfo->SquelchOpenNoiseThresh,   gRxVfo->SquelchCloseNoiseThresh,
        gRxVfo->SquelchCloseGlitchThresh, gRxVfo->SquelchOpenGlitchThresh);

    BK4819_PickRXFilterPathBasedOnFrequency(Frequency);

    // what does this in do ?
    BK4819_ToggleGpioOut(BK4819_GPIO0_PIN28_RX_ENABLE, true);

    // AF RX Gain and DAC
    //BK4819_WriteRegister(BK4819_REG_48, 0xB3A8);  // 1011 00 111010 1000
    BK4819_WriteRegister(BK4819_REG_48,
        (11u << 12)                 |     // ??? .. 0 ~ 15, doesn't seem to make any difference
        ( 0u << 10)                 |     // AF Rx Gain-1
        (gEeprom.VOLUME_GAIN << 4) |     // AF Rx Gain-2
        (gEeprom.DAC_GAIN    << 0));     // AF DAC Gain (after Gain-1 and Gain-2)


    uint16_t InterruptMask = BK4819_REG_3F_SQUELCH_FOUND | BK4819_REG_3F_SQUELCH_LOST;

    #ifdef ENABLE_NOAA
        if (!IS_NOAA_CHANNEL(gRxVfo->CHANNEL_SAVE))
    #endif
    {
        if (gRxVfo->Modulation == MODULATION_FM)
        {   // FM
            uint8_t CodeType = gRxVfo->pRX->CodeType;
            uint8_t Code     = gRxVfo->pRX->Code;

            switch (CodeType)
            {
                default:
                case CODE_TYPE_OFF:
                    BK4819_SetCTCSSFrequency(SQL_TONE);
                    BK4819_SetTailDetection(SQL_TONE); // Default 550 = QS's 55Hz tone method

                    InterruptMask = BK4819_REG_3F_CxCSS_TAIL | BK4819_REG_3F_SQUELCH_FOUND | BK4819_REG_3F_SQUELCH_LOST;
                    break;

                case CODE_TYPE_CONTINUOUS_TONE:
                    BK4819_SetCTCSSFrequency(CTCSS_Options[Code]);

                    //#ifndef ENABLE_CTCSS_TAIL_PHASE_SHIFT
                    //    BK4819_SetTailDetection(550);       // QS's 55Hz tone method
                    //#else
                    //  BK4819_SetTailDetection(CTCSS_Options[Code]);
                    //#endif

                    InterruptMask = 0
                        | BK4819_REG_3F_CxCSS_TAIL
                        | BK4819_REG_3F_CTCSS_FOUND
                        | BK4819_REG_3F_CTCSS_LOST
                        | BK4819_REG_3F_SQUELCH_FOUND
                        | BK4819_REG_3F_SQUELCH_LOST;

                    break;

                case CODE_TYPE_DIGITAL:
                case CODE_TYPE_REVERSE_DIGITAL:
                    BK4819_SetCDCSSCodeWord(DCS_GetGolayCodeWord(CodeType, Code));
                    InterruptMask = 0
                        | BK4819_REG_3F_CxCSS_TAIL
                        | BK4819_REG_3F_CDCSS_FOUND
                        | BK4819_REG_3F_CDCSS_LOST
                        | BK4819_REG_3F_SQUELCH_FOUND
                        | BK4819_REG_3F_SQUELCH_LOST;
                    break;
            }

#ifndef ENABLE_FEAT_F4HWN
            if (gRxVfo->SCRAMBLING_TYPE > 0 && gSetting_ScrambleEnable)
                BK4819_EnableScramble(gRxVfo->SCRAMBLING_TYPE - 1);
            else
                BK4819_DisableScramble();
#else
                BK4819_DisableScramble();
#endif
        }
    }
    #ifdef ENABLE_NOAA
        else
        {
            BK4819_SetCTCSSFrequency(2625);
            InterruptMask = 0
                | BK4819_REG_3F_CTCSS_FOUND
                | BK4819_REG_3F_CTCSS_LOST
                | BK4819_REG_3F_SQUELCH_FOUND
                | BK4819_REG_3F_SQUELCH_LOST;
        }
    #endif

#ifdef ENABLE_VOX
    if (gEeprom.VOX_SWITCH  && gCurrentVfo->Modulation == MODULATION_FM
#ifdef ENABLE_NOAA
        && !IS_NOAA_CHANNEL(gCurrentVfo->CHANNEL_SAVE)
#endif
#ifdef ENABLE_FMRADIO
        && !gFmRadioMode
#endif
    ){
        BK4819_EnableVox(gEeprom.VOX1_THRESHOLD, gEeprom.VOX0_THRESHOLD);
        InterruptMask |= BK4819_REG_3F_VOX_FOUND | BK4819_REG_3F_VOX_LOST;
    }
    else
#endif
    {
        BK4819_DisableVox();
    }

    // RX expander
    BK4819_SetCompander((gRxVfo->Modulation == MODULATION_FM && gRxVfo->Compander >= 2) ? gRxVfo->Compander : 0);

    BK4819_EnableDTMF();
    InterruptMask |= BK4819_REG_3F_DTMF_5TONE_FOUND;

    RADIO_SetupAGC(gRxVfo->Modulation == MODULATION_AM, false);

    // enable/disable BK4819 selected interrupts
    BK4819_WriteRegister(BK4819_REG_3F, InterruptMask);

    FUNCTION_Init();

    if (switchToForeground)
        FUNCTION_Select(FUNCTION_FOREGROUND);
}

#ifdef ENABLE_NOAA
    void RADIO_ConfigureNOAA(void)
    {
        uint8_t ChanAB;

        gUpdateStatus = true;

        if (gEeprom.NOAA_AUTO_SCAN)
        {
            if (gEeprom.DUAL_WATCH != DUAL_WATCH_OFF)
            {
                if (!IS_NOAA_CHANNEL(gEeprom.ScreenChannel[0]))
                {
                    if (!IS_NOAA_CHANNEL(gEeprom.ScreenChannel[1]))
                    {
                        gIsNoaaMode = false;
                        return;
                    }
                    ChanAB = 1;
                }
                else
                    ChanAB = 0;

                if (!gIsNoaaMode)
                    gNoaaChannel = gEeprom.VfoInfo[ChanAB].CHANNEL_SAVE - NOAA_CHANNEL_FIRST;

                gIsNoaaMode = true;
                return;
            }

            if (IS_NOAA_CHANNEL(gRxVfo->CHANNEL_SAVE))
            {
                gIsNoaaMode          = true;
                gNoaaChannel         = gRxVfo->CHANNEL_SAVE - NOAA_CHANNEL_FIRST;
                gNOAA_Countdown_10ms = NOAA_countdown_2_10ms;
                gScheduleNOAA        = false;
            }
            else
                gIsNoaaMode = false;
        }
        else
            gIsNoaaMode = false;
    }
#endif

void RADIO_SetTxParameters(void)
{
    BK4819_FilterBandwidth_t Bandwidth = gCurrentVfo->CHANNEL_BANDWIDTH;

    #ifdef ENABLE_FEAT_F4HWN_NARROWER
        if(Bandwidth == BK4819_FILTER_BW_NARROW && gSetting_set_nfm == 1)
        {
            Bandwidth = BK4819_FILTER_BW_NARROWER;
        }
    #endif

    AUDIO_AudioPathOff();

    gEnableSpeaker = false;

    BK4819_ToggleGpioOut(BK4819_GPIO0_PIN28_RX_ENABLE, false);

    switch (Bandwidth)
    {
        default:
            Bandwidth = BK4819_FILTER_BW_WIDE;
            [[fallthrough]];
        case BK4819_FILTER_BW_WIDE:
        case BK4819_FILTER_BW_NARROW:
        case BK4819_FILTER_BW_NARROWER:
            #ifdef ENABLE_AM_FIX
//              BK4819_SetFilterBandwidth(Bandwidth, gCurrentVfo->Modulation == MODULATION_AM && gSetting_AM_fix);
                BK4819_SetFilterBandwidth(Bandwidth, true);
            #else
                BK4819_SetFilterBandwidth(Bandwidth, false);
            #endif
            break;
    }

    BK4819_SetFrequency(gCurrentVfo->pTX->Frequency);

    // TX compressor
    BK4819_SetCompander((gRxVfo->Modulation == MODULATION_FM && (gRxVfo->Compander == 1 || gRxVfo->Compander >= 3)) ? gRxVfo->Compander : 0);

    BK4819_PrepareTransmit();

    SYSTEM_DelayMs(10);

    BK4819_PickRXFilterPathBasedOnFrequency(gCurrentVfo->pTX->Frequency);

    BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, true);

    SYSTEM_DelayMs(5);

    BK4819_SetupPowerAmplifier(gCurrentVfo->TXP_CalculatedSetting, gCurrentVfo->pTX->Frequency);

    SYSTEM_DelayMs(10);

    switch (gCurrentVfo->pTX->CodeType)
    {
        default:
        case CODE_TYPE_OFF:
            BK4819_ExitSubAu();
            break;

        case CODE_TYPE_CONTINUOUS_TONE:
            BK4819_SetCTCSSFrequency(CTCSS_Options[gCurrentVfo->pTX->Code]);
            break;

        case CODE_TYPE_DIGITAL:
        case CODE_TYPE_REVERSE_DIGITAL:
            BK4819_SetCDCSSCodeWord(DCS_GetGolayCodeWord(gCurrentVfo->pTX->CodeType, gCurrentVfo->pTX->Code));
            break;
    }
}

void RADIO_SetModulation(ModulationMode_t modulation)
{
    BK4819_AF_Type_t mod;
    switch(modulation) {
        default:
        case MODULATION_FM:
            mod = BK4819_AF_FM;
            break;
        case MODULATION_AM:
            mod = BK4819_AF_AM;
            break;
        case MODULATION_USB:
            mod = BK4819_AF_BASEBAND2;
            break;

#ifdef ENABLE_BYP_RAW_DEMODULATORS
        case MODULATION_BYP:
            mod = BK4819_AF_UNKNOWN3;
            break;
        case MODULATION_RAW:
            mod = BK4819_AF_BASEBAND1;
            break;
#endif
    }

    BK4819_SetAF(mod);

    BK4819_SetRegValue(afDacGainRegSpec, 0xF);
    BK4819_WriteRegister(BK4819_REG_3D, modulation == MODULATION_USB ? 0 : 0x2AAB);
    BK4819_SetRegValue(afcDisableRegSpec, modulation != MODULATION_FM);

    RADIO_SetupAGC(modulation == MODULATION_AM, false);
}

void RADIO_SetupAGC(bool listeningAM, bool disable)
{
    static uint8_t lastSettings;
    uint8_t newSettings = (listeningAM << 1) | disable;
    if(lastSettings == newSettings)
        return;
    lastSettings = newSettings;


    if(!listeningAM) { // if not actively listening AM we don't need any AM specific regulation
        BK4819_SetAGC(!disable);
        BK4819_InitAGC(false);
    }
    else {
#ifdef ENABLE_AM_FIX
        if(gSetting_AM_fix) { // if AM fix active lock AGC so AM-fix can do it's job
            BK4819_SetAGC(0);
            AM_fix_enable(!disable);
        }
        else
#endif
        {
            BK4819_SetAGC(!disable);
            BK4819_InitAGC(true);
        }
    }
}

void RADIO_SetVfoState(VfoState_t State)
{
    if (State == VFO_STATE_NORMAL) {
        VfoState[0] = VFO_STATE_NORMAL;
        VfoState[1] = VFO_STATE_NORMAL;
    } else if (State == VFO_STATE_VOLTAGE_HIGH) {
        VfoState[0] = VFO_STATE_VOLTAGE_HIGH;
        VfoState[1] = VFO_STATE_TX_DISABLE;
    } else {
        // 1of11
        const unsigned int vfo = (gEeprom.CROSS_BAND_RX_TX == CROSS_BAND_OFF) ? gEeprom.RX_VFO : gEeprom.TX_VFO;
        VfoState[vfo] = State;
    }

    gVFOStateResumeCountdown_500ms = (State == VFO_STATE_NORMAL) ? 0 : vfo_state_resume_countdown_500ms;
    gUpdateDisplay = true;
}


void RADIO_PrepareTX(void)
{
    VfoState_t State = VFO_STATE_NORMAL;  // default to OK to TX

    if (gEeprom.DUAL_WATCH != DUAL_WATCH_OFF)
    {   // dual-RX is enabled

        gDualWatchCountdown_10ms = dual_watch_count_after_tx_10ms;
        gScheduleDualWatch       = false;

        if (!gRxVfoIsActive)
        {   // use the current RX vfo
            gEeprom.RX_VFO = gEeprom.TX_VFO;
            gRxVfo         = gTxVfo;
            gRxVfoIsActive = true;
        }

        // let the user see that DW is not active
        gDualWatchActive = false;
        gUpdateStatus    = true;
    }

    RADIO_SelectCurrentVfo();

#ifdef ENABLE_FEAT_F4HWN
        if(TX_freq_check(gCurrentVfo->pTX->Frequency) != 0 && gCurrentVfo->TX_LOCK == true
    #if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
            && gAlarmState != ALARM_STATE_SITE_ALARM
    #endif
#else
        if(TX_freq_check(gCurrentVfo->pTX->Frequency) != 0
    #if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
            && gAlarmState != ALARM_STATE_SITE_ALARM
    #endif
#endif
    ){
        // TX frequency not allowed
        State = VFO_STATE_TX_DISABLE;
        gVfoConfigureMode = VFO_CONFIGURE;
    } else if (SerialConfigInProgress()) {
        // TX is disabled or config upload/download in progress
        State = VFO_STATE_TX_DISABLE;
    } else if (gCurrentVfo->BUSY_CHANNEL_LOCK && gCurrentFunction == FUNCTION_RECEIVE) {
        // busy RX'ing a station
        State = VFO_STATE_BUSY;
    } else if (gBatteryDisplayLevel == 0) {
        // charge your battery !git co
        State = VFO_STATE_BAT_LOW;
    } else if (gBatteryDisplayLevel > 6) {
        // over voltage .. this is being a pain
        State = VFO_STATE_VOLTAGE_HIGH;
    }
#ifndef ENABLE_TX_WHEN_AM
    else if (gCurrentVfo->Modulation != MODULATION_FM) {
        // not allowed to TX if in AM mode
        State = VFO_STATE_TX_DISABLE;
    }
#endif

    if (State != VFO_STATE_NORMAL) {
        // TX not allowed
        RADIO_SetVfoState(State);

#if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
        gAlarmState = ALARM_STATE_OFF;
#endif

#ifdef ENABLE_DTMF_CALLING
        gDTMF_ReplyState = DTMF_REPLY_NONE;
#endif
        AUDIO_PlayBeep(BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL);
        return;
    }

    // TX is allowed

#ifdef ENABLE_DTMF_CALLING
    if (gDTMF_ReplyState == DTMF_REPLY_ANI)
    {
        gDTMF_IsTx = gDTMF_CallMode == DTMF_CALL_MODE_DTMF;

        if (gDTMF_IsTx) {
            gDTMF_CallState = DTMF_CALL_STATE_NONE;
            gDTMF_TxStopCountdown_500ms = DTMF_txstop_countdown_500ms;
        } else {
            gDTMF_CallState = DTMF_CALL_STATE_CALL_OUT;
        }
    }
#endif

    FUNCTION_Select(FUNCTION_TRANSMIT);

    gTxTimerCountdown_500ms = 0;            // no timeout

    #if defined(ENABLE_ALARM) || defined(ENABLE_TX1750)
    if (gAlarmState == ALARM_STATE_OFF)
    #endif
    {

        gTxTimerCountdown_500ms = ((gEeprom.TX_TIMEOUT_TIMER + 1) * 5) * 2;

        /*
        if (gEeprom.TX_TIMEOUT_TIMER == 0)
            gTxTimerCountdown_500ms = 60;   // 30 sec
        else if (gEeprom.TX_TIMEOUT_TIMER < (ARRAY_SIZE(gSubMenu_TOT) - 1))
            gTxTimerCountdown_500ms = 120 * gEeprom.TX_TIMEOUT_TIMER;  // minutes
        else
            gTxTimerCountdown_500ms = 120 * 15;  // 15 minutes
        */

#ifdef ENABLE_FEAT_F4HWN 
        gTxTimerCountdownAlert_500ms = gTxTimerCountdown_500ms;
#endif
    }

    gTxTimeoutReached    = false;

#ifdef ENABLE_FEAT_F4HWN 
    gTxTimeoutReachedAlert = false;
#endif
    
    gFlagEndTransmission = false;
    gRTTECountdown_10ms  = 0;

#ifdef ENABLE_DTMF_CALLING
    gDTMF_ReplyState     = DTMF_REPLY_NONE;
#endif
}

void RADIO_SendCssTail(void)
{
    switch (gCurrentVfo->pTX->CodeType) {
    case CODE_TYPE_DIGITAL:
    case CODE_TYPE_REVERSE_DIGITAL:
        BK4819_PlayCDCSSTail();
        break;
    default:
        BK4819_PlayCTCSSTail();
        break;
    }

    SYSTEM_DelayMs(200);
}

void RADIO_SendEndOfTransmission(void)
{
    BK4819_PlayRoger();
    DTMF_SendEndOfTransmission();

    // send the CTCSS/DCS tail tone - allows the receivers to mute the usual FM squelch tail/crash
    if(gEeprom.TAIL_TONE_ELIMINATION)
        RADIO_SendCssTail();
    RADIO_SetupRegisters(false);
}

void RADIO_PrepareCssTX(void)
{
    RADIO_PrepareTX();

    SYSTEM_DelayMs(200);

    if(gEeprom.TAIL_TONE_ELIMINATION)
        RADIO_SendCssTail();
    RADIO_SetupRegisters(true);
}
